package com.zzh.lib.config;

import android.content.Context;

import com.zzh.lib.cache.define.Cache;
import com.zzh.lib.cache.define.FastEncoder;
import com.zzh.lib.cache.define.FastLogger;
import com.zzh.lib.cache.define.HConverter;
import com.zzh.lib.cache.encipher.FastCipher;
import com.zzh.lib.cache.fast.FastCache;
import com.zzh.lib.cache.fast.HDefaultConverter;
import com.zzh.lib.cache.store.InternalDiskCacheStore;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class CacheConfig {
    private static CacheConfig sConfig;
    public final Context mContext;
    static volatile Executor sExecutor;
    public FastLogger mLogger;

    public final Cache.CacheStore mDiskCacheStore;
    public List<FastEncoder> encoders;
    private static final Map<String, FastCache> INSTANCE_MAP = new ConcurrentHashMap<>();
    private boolean isDebug = false;
    private HConverter converter;

    public HConverter getConverter() {
        if (converter == null) {
            converter = new HDefaultConverter();
        }
        return converter;
    }

    public void setConverter(HConverter converter) {
        this.converter = converter;
    }

    public boolean isDebug() {
        return isDebug;
    }

    public CacheConfig setDebug(boolean debug) {
        isDebug = debug;
        return this;
    }

    private int writingMode;
    private int internalLimit = 2048;
    private FastCipher cipher = null;

    private CacheConfig(Builder builder) {
        mContext = builder.mContext;
        mDiskCacheStore = builder.getDiskCacheStore();
        sExecutor = builder.sExecutor == null ? getExecutors() : builder.sExecutor;
        encoders = builder.encoders;
        writingMode = builder.writingMode;
        mLogger = builder.mLogger;
        this.converter = builder.getConverter();
    }

    public FastCipher getCipher() {
        return cipher;
    }

    public CacheConfig setCipher(FastCipher cipher) {
        this.cipher = cipher;
        return this;
    }

    public static void setInternalLimit(int limit) {
        if (limit >= 2048 && limit <= 0xFFFF) {
            get().internalLimit = limit;
        }
    }

    public int getInternalLimit() {
        return internalLimit;
    }

    public static Executor getExecutor() {
        return sExecutor;
    }

    public FastCache build() {
        String key = mDiskCacheStore.getCacheFilePath() + File.separator + mDiskCacheStore.getCacheFileName();
        FastCache kv = INSTANCE_MAP.get(key);
        if (kv == null) {
            synchronized (Builder.class) {
                kv = INSTANCE_MAP.get(key);
                if (kv == null) {
                    kv = new FastCache(mDiskCacheStore.getCacheFilePath(),
                            mDiskCacheStore.getCacheFileName(),
                            encoders, cipher,
                            writingMode);
                    INSTANCE_MAP.put(key, kv);
                }
            }
        }
        return kv;
    }

    /**
     * 初始化
     *
     * @param config
     */
    public static synchronized void init(CacheConfig config) {
        if (config == null)
            throw new IllegalArgumentException("config is null");

        if (sConfig != null)
            throw new RuntimeException(CacheConfig.class.getCanonicalName() + " has been init");

        sConfig = config;
    }

    /**
     * 返回配置
     *
     * @return
     */
    public static CacheConfig get() {
        if (sConfig == null)
            throw new RuntimeException(CacheConfig.class.getCanonicalName() + " has not been init");
        return sConfig;
    }


    public static final class Builder {

        private FastLogger mLogger;
        private Context mContext;
        private Cache.CacheStore mDiskCacheStore;
        private Executor sExecutor;
        private List<FastEncoder> encoders;
        private int writingMode;
        private FastCipher cipher = null;
        private HConverter converter;

        public HConverter getConverter() {
            return converter;
        }

        public void setConverter(HConverter converter) {
            this.converter = converter;
        }

        public FastCipher getCipher() {
            return cipher;
        }

        public Builder setCipher(FastCipher cipher) {
            this.cipher = cipher;
            return this;
        }

        public Cache.CacheStore getDiskCacheStore() {
            return mDiskCacheStore = mDiskCacheStore != null ? mDiskCacheStore : new InternalDiskCacheStore(mContext);
        }

        public Builder setLogger(FastLogger logger) {
            mLogger = logger;
            return this;
        }

        /**
         * 设置本地磁盘缓存
         *
         * @param store
         * @return
         */
        public Builder setDiskCacheStore(Cache.CacheStore store) {
            mDiskCacheStore = store;
            return this;
        }

        /**
         * 设置线程池
         *
         * @param sExecutor 线程池
         * @return Builder 实例
         */
        public Builder setExecutor(Executor sExecutor) {
            this.sExecutor = sExecutor;
            return this;
        }


        /**
         * 设置序列化实例
         *
         * @param encoders
         * @return Builder
         */
        public Builder setEncoders(List<FastEncoder> encoders) {
            if (this.encoders == null) {
                this.encoders = encoders;
            } else {
                this.encoders.addAll(encoders);
            }

            return this;
        }

        /**
         * 注册序列化工具
         *
         * @param encoders 序列化
         * @return Builder
         */
        public <T> Builder registerEncoders(FastEncoder<T> encoders) {
            if (this.encoders == null) {
                this.encoders = new ArrayList<>();
            }
            this.encoders.add(encoders);
            return this;
        }

        public CacheConfig build(Context context) {
            mContext = context.getApplicationContext();
            return new CacheConfig(this);
        }

        /**
         * Assigned writing mode to SYNC_BLOCKING.
         * <p>
         * In non-blocking mode (write data with mmap),
         * it might loss update if the system crash or power off before flush data to disk.
         * You could use {@link FastCache#force()} to avoid loss update, or use SYNC_BLOCKING mode.
         * <p>
         * In blocking mode, every update will write all data to the file, which is expensive cost.
         * <p>
         * So it's recommended to use blocking mode only if the data is every important.
         * <p>
         *
         * @return the builder
         */
        public CacheConfig.Builder blocking() {
            writingMode = FastCache.SYNC_BLOCKING;
            return this;
        }


        /**
         * Similar to {@link #blocking()}, but put writing task to async thread.
         *
         * @return the builder
         */
        public CacheConfig.Builder asyncBlocking() {
            writingMode = FastCache.SYNC_BLOCKING;
            return this;
        }


    }

    static Executor getExecutors() {
        if (sExecutor == null) {
            synchronized (CacheConfig.class) {
                if (sExecutor == null) {
                    ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 4,
                            10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
                    executor.allowCoreThreadTimeOut(true);
                    sExecutor = executor;
                }
            }
        }
        return sExecutor;
    }

    public static class NumberType {
        public static final int INT = 0;
        public static final int LONG = 1;
        public static final int FLOAT = 2;
        public static final int DOUBLE = 3;
    }
}
